export interface TableData{
    id:number,
    name:string,
    cells:string[][],
    merges?:MergeInfo[],
    colors?:CellColorInfo[]
    sticky?:StickyInfo
}

export interface MergeInfo{ 
    row:number,
    col:number,
    down:number,
    right:number
}
export interface CellColorInfo{
    pos:PositionCompact,
    bg?:string,
    text?:string
}
export const cellBgColorDefault = '#eeeeee'
export const cellTextColorDefault = '#000000'
export interface StickyInfo{
    //均为1开始的index
    topIdx?:number,//第几个行吸附顶部
    topFromColIdx?:number,//从第几个列开始吸附顶部
    leftIdx?:number,//第几个列吸附左侧
    leftFromRowIdx?:number//从第几个行开始吸附左侧
}

export interface Position{
    row:number,
    col:number
}
export type PositionCompact = [row:number, col:number]
export type Selection=Position[]
export type Direction = "vertical"|"horizontal";
export type SingleDirection = "left"|"right"|"top"|"bottom"
export interface TableRange{
    type:Direction,
    min:number,
    max:number
}

//┌───┬───┬───┐ ←gap{dir:"horizontal",idx:0}
//| o | o | o | ←cell{row:0}
//├───┼───┼───┤ ←gap{dir:"horizontal",idx:1}
//| o | o | o | ←cell{row:1}
//└───┴───┴───┘ ←gap{dir:"horizontal",idx:2}
//↑ ↑ ↑
//│ │ └gap{dir:"vertical",idx:1}
//│ └cell{col:0}
//└gap{dir:"vertical",idx:0}

export interface TableGap{
    dir:Direction,
    idx:number
}

export interface MergedCellInfo extends Position{
    fromMerge:MergeInfo
}

export interface CellShown{
    content:string,
    position:Position,
    rowspan?:number,
    colspan?:number,
    bgColor?:string,
    textColor?:string
}

export class Table implements TableData{
    id: number
    name: string
    cells: string[][]
    cellsShown: CellShown[][]
    merges: MergeInfo[]
    merged: MergedCellInfo[]
    colors: CellColorInfo[]
    sticky: StickyInfo
    rowCount:number
    colCount:number
    constructor(data:TableData){
        this.id = data.id;
        this.name = data.name;
        this.cells = data.cells || [];
        this.merges = data.merges || [];
        this.merged = [];
        this.colors = data.colors || [];
        this.sticky = data.sticky || {};
        this.rowCount = this.cells.length;
        this.colCount = Math.max(...this.cells.map(r=>r.length));
        this.cellsShown = [];
        this.totalRefresh();
    }
    //输出
    output(){
        const res:CellShown[][] = [];
        var line:CellShown[] = [];
        this.enumerateCells((r,c)=>{
            var hidden = false;
            var colspan = 1;
            var rowspan = 1;
            if(this.isMergedCell(r,c)){
                const m = this.isMergeInfoHead(r,c)
                if(!m){
                    hidden = true;
                }else{
                    colspan = m.right+1;
                    rowspan = m.down+1;
                }
            }
            if(!hidden){
                const colorHere = this.colors.find(ci => ci.pos[0]==r && ci.pos[1]==c)
                line.push({
                    content:this.cells[r][c],
                    colspan: colspan > 1 ? colspan : undefined,
                    rowspan: rowspan > 1 ? rowspan : undefined,
                    position:{row:r,col:c},
                    bgColor: colorHere?.bg,
                    textColor: colorHere?.text
                });
            }
            if(c==this.colCount-1){
                res.push(line);
                line=[];
            }
            return true;
        })
        return res;
    }
    getData():TableData{
        return {
            id:this.id,
            name:this.name,
            cells:this.cells,
            merges:this.merges,
            colors:this.colors,
            sticky:this.sticky
        }
    }
    replaceData(data:TableData){
        this.id = data.id;
        this.name = data.name;
        this.cells = data.cells;
        this.merges = data.merges || []; 
        this.colors = data.colors || [];
        this.sticky = data.sticky || {};
        this.totalRefresh();
    }
    refreshCellShown(){
        this.cellsShown = this.output();
    }
    refreshSize(){
        this.rowCount = this.cells.length;
        this.colCount = Math.max(...this.cells.map(r=>r.length));
    }
    totalRefresh(){
        //只有一格大的合并组还有什么存在的必要吗
        this.merges = this.merges.filter(x=>x.down>0||x.right>0);
        this.rmColorRedundant()
        this.refreshSize();
        this.cells.forEach(row=>{
            const smallBy = this.colCount - row.length;
            if(smallBy!=0){
                row.push(...this.newStringArr(smallBy))
            }
        })        
        this.refreshMergedPositions();
        this.refreshCellShown();
    }
    rmColorRedundant(){
        this.colors = this.colors.filter(cl=>(cl.bg || cl.text) && (cl.bg !== cellBgColorDefault || cl.text !== cellTextColorDefault))
    }

    //选区和合并单元格
    isMergeDangerous(s:Selection){
        var s = this.rectanglifySelection(s,[]);
        var head = s[0];
        s.forEach(p=>{
            if(p.row <head.row || p.col<head.col){
                head = p;
            }
        });
        var danger = false;
        s.forEach(p=>{
            if(head!=p){
                if(this.cells[p.row][p.col]){
                    danger = true;
                }
            }
        })
        return danger;
    }
    addMerge(s:Selection):Selection{
        if(s.length<=1){return s;}
        const related:MergeInfo[]=[];
        s = this.rectanglifySelection(s,related);
        this.rmColorRedundant()
        s.sort(this.posCmp);
        var head = s[0];
        var headValue = this.cells[head.row][head.col];
        var headColor = this.colors.find(cl=>cl.pos[0]===head.row && cl.pos[1]===head.col)
        const headHaveColor = !!headColor
        s.forEach(p=>{
            if(head!=p){
                if(!headValue){
                    const value = this.cells[p.row][p.col];
                    if(value){
                        headValue = value;
                    }
                }
                this.cells[p.row][p.col]=""

                const rmColorIdx = this.colors.findIndex(cl=>cl.pos[0]===p.row && cl.pos[1]===p.col)
                if(rmColorIdx>=0){
                    if(!headColor){
                        headColor = this.colors[rmColorIdx];
                    }
                    this.colors.splice(rmColorIdx,1);
                }
            }
        })
        this.cells[head.row][head.col]=headValue;
        if(!headHaveColor && headColor){
            headColor.pos = [head.row, head.col];
            this.colors.push(headColor)
        }

        this.merges = this.merges.filter(m=>related.indexOf(m)==-1);
        const newMergeInfo = this.selectionToMergeInfo(s);
        this.merges.push(newMergeInfo)
        this.refreshMergedPositions();
        this.refreshCellShown();
        return [
            {
                row:newMergeInfo.row,
                col:newMergeInfo.col
            }
        ]
    }
    removeMerge(p:Position){
        const m = this.merged.find(m=>m.row==p.row && m.col==p.col)?.fromMerge;
        if(!m){return}
        this.merges = this.merges.filter(x=>x.row!=m.row || x.col!=m.col)
        this.refreshMergedPositions();
        this.refreshCellShown();
    }
    isMergedCell(r:number,c:number){
        return this.merged.find(p=>p.row==r && p.col==c)
    }
    isMergeInfoHead(r:number,c:number){
        return this.merges.find(m=>m.row==r && m.col==c)
    }
    isValidMergeInfo(m:MergeInfo){
        if(m.col>0 && m.right>0 && m.col+m.right<this.colCount){
            if(m.row>0 && m.down>0 && m.row+m.down<this.rowCount){
                return true;
            }
        }
        return false;
    }
    rectanglifySelection(s:Selection,related:MergeInfo[]):Selection{
        const cells:Position[]=[];
        s.forEach(({row,col})=>{
            cells.push({row,col})
            const info = this.isMergedCell(row,col)
            if(info && related.indexOf(info.fromMerge)==-1){
                related.push(info.fromMerge);
            }
        })
        related.forEach(m=>{
            this.enumerateMergeInfo(m,(row,col)=>{
                cells.push({row,col})
                return true;
            })
        })

        const rectArea = this.selectionToMergeInfo(cells as Selection);
        const rect:Selection=[];
        this.enumerateMergeInfo(rectArea,(row,col)=>{
            rect.push({row,col});return true;
        })
        if(this.isSameSelection(s,rect)){
            //没有产生效果，说明可以结束递归
            return s;
        }else{
            //有效果，但无法保证不会继续有效果
            return this.rectanglifySelection(rect,related);
        }
    }
    selectionToMergeInfo(s:Selection):MergeInfo{
        const allCols = s.map(c=>c.col);
        const allRows = s.map(c=>c.row);
        const left = Math.min(...allCols);
        const right = Math.max(...allCols);
        const top = Math.min(...allRows);
        const bottom = Math.max(...allRows);
        return {
            row:top,
            down:bottom-top,
            col:left,
            right:right-left
        };
    }
    private isSameSelection(s1:Selection,s2:Selection):boolean{
        if(s1.length!=s2.length){return false;}
        s1.sort(this.posCmp);
        s2.sort(this.posCmp);
        for(var i=0;i<s1.length;i++){
            if(s1[i].row!=s2[i].row || s1[i].col!=s2[i].col){
                return false;
            }
        }
        return true;
    }
    private posCmp(a:Position,b:Position){
        if(a.row==b.row){
            return a.col-b.col
        }
        return a.row-b.row;
    }
    private rangeIncludeWholeMerge(r:TableRange,m:MergeInfo):boolean{
        if(r.type=="horizontal"){
            if(r.min<=m.col && r.max>=m.col+m.right){
                return true;
            }
        }else if(r.type=="vertical"){
            if(r.min<=m.row && r.max>=m.row+m.down){
                return true;
            }
        }
        return false;
    }
    private rangeIncludeCell(r:TableRange,row:number,col:number){
        if(r.type=="horizontal"){
            if(r.min<=col && r.max>=col){
                return true;
            }
        }else if(r.type=="vertical"){
            if(r.min<=row && r.max>=row){
                return true;
            }
        }
        return false;
    }
    refreshMergedPositions(){
        this.merged = [];
        this.merges.forEach(m=>{
            this.enumerateMergeInfo(m,(r,c)=>{
                this.merged.push({
                    row:r,
                    col:c,
                    fromMerge:m
                })
                return true;
            })
        })
        this.merged.sort(this.posCmp)
    }
    enumerateCells(func:(r:number,c:number)=>boolean){
        for(var r=0;r<this.rowCount;r++){
            for(var c=0;c<this.colCount;c++){
                if(!func(r,c)){return;}
            }
        }
    }
    enumerateMergeInfo(s:MergeInfo, func:(r:number,c:number)=>boolean){
        for(var r=s.row;r<=s.row+s.down;r++){
            for(var c=s.col;c<=s.col+s.right;c++){
                if(!func(r,c)){return;}
            }
        }
    }
    enumerateRange(r:TableRange, func:(r:number,c:number)=>boolean){
        if(r.type=="vertical"){
            for(var i=r.min;i<=r.max;i++){
                for(var j=0;j<this.colCount;j++){
                    if(!func(i,j)){
                        return;
                    }
                }
            }
        }
        else if(r.type=="horizontal"){
            for(var i=0; i<this.rowCount; i++){
                for(var j=r.min; j<=r.max; j++){
                    if(!func(i,j)){
                        return;
                    }
                }
            }
        }
    }
    enumerateRow(rowIdx:number, func:(r:number,c:number)=>boolean){
        const row = this.cells.at(rowIdx);
        if(!row){return;}
        for(let c=0;c<row.length;c++){
            if(!func(rowIdx, c)){
                return;
            }
        }
    }
    enumerateCol(colIdx:number, func:(r:number,c:number)=>boolean){
        for(let r=0; r<this.cells.length; r++){
            if(!func(r, colIdx)){
                return;
            }
        }
    }
    getSelectionRange(s:Selection,type:Direction):TableRange{
        var min=0,max=0;
        if(s.length==1){
            const m = this.isMergeInfoHead(s[0].row,s[0].col);
            if(m){
                s.push({row:m.row + m.down,col:m.col + m.right})
            }
        }
        if(type=="horizontal"){
            min = Math.min(...s.map(x=>x.col));
            max = Math.max(...s.map(x=>x.col));
        }
        else if(type=="vertical"){
            min = Math.min(...s.map(x=>x.row));
            max = Math.max(...s.map(x=>x.row));
        }
        return {min,max,type}
    }
    isRectSelection(s:Selection){
        const ss = [...s];
        const considered:MergeInfo[] = [];
        s.forEach(p=>{
            const m = this.isMergedCell(p.row,p.col)?.fromMerge;
            if(m){
                if(considered.includes(m)){
                    return;
                }
                considered.push(m);
                this.enumerateMergeInfo(m,(row,col)=>{ss.push({row,col});return true;})
            }
        })
        const m = this.selectionToMergeInfo(ss);
        var res = true;
        this.enumerateMergeInfo(m,(r,c)=>{
            if(ss.find(x=>x.row==r && x.col==c)){
                return true;
            }
            res = false;
            return false;
        })
        return res;
    }
    isInsideSingleMergeSelection(s:Selection){
        var mHead:MergeInfo|undefined;
        for(var i=0;i<s.length;i++){
            const m = this.isMergedCell(s[i].row,s[i].col)?.fromMerge;
            if(m){
                if(!mHead){
                    mHead = m;
                }else{
                    if(mHead != m){
                        return false;
                    }
                }
            }
            else
            {
                return false;
            }
        }
        return true
    }
    expandSelection(s:Selection,direction:Direction,mode:"loose"|"strict"):Selection{
        if(mode=="strict"){
            if(direction=="horizontal"){
                const rv = this.getSelectionRange(s,"vertical");
                const p1:Position = {row:rv.min,col:0};
                const p2:Position = {row:rv.max,col:this.colCount-1};
                return this.rectanglifySelection([...s,p1,p2],[]);
            }
            else if(direction=="vertical"){
                const rh = this.getSelectionRange(s,"horizontal");
                const p1:Position = {row:0,col:rh.min};
                const p2:Position = {row:this.rowCount-1,col:rh.max};
                return this.rectanglifySelection([...s,p1,p2],[]);
            }
        }
        else if(mode=="loose"){
            var related:MergeInfo[] = [];
            var newS:Selection = [];
            var rangeType:Direction = direction=="horizontal"?"vertical":"horizontal";

            const ss:Selection=[];
            ss.push(...s);
            s.forEach(p=>{
                const m = this.isMergeInfoHead(p.row,p.col);
                if(m){
                    this.enumerateMergeInfo(m,(row,col)=>{
                        ss.push({row,col});return true;
                    })
                }
            })
            var range = this.getSelectionRange(ss,rangeType);
            this.enumerateRange(range,(r,c)=>{
                const m = this.isMergedCell(r,c);
                if(m){
                    related.push(m.fromMerge);
                }
                else{
                    newS.push({row:r,col:c});
                }
                return true;
            })
            related = related.filter(m=>this.rangeIncludeWholeMerge(range,m));
            related.forEach(m=>{
                if(!newS.find(p=>p.col==m.col&&p.row==m.row)){
                    newS.push({row:m.row,col:m.col})
                }
            })
            if(newS){
                return newS;
            }
            else{
                return s;
            }
        }
        return s;
    }
    selectSpecificIdx(direction:Direction,zero_based_idx:number):Selection{
        const newS:Selection = [];
        if(direction=="horizontal"){
            const r = zero_based_idx;
            for(var i=0;i<this.colCount;i++){
                const m = this.isMergedCell(r,i);
                if(!m){
                    newS.push({row:r,col:i})
                }else if(m.fromMerge.down==0){
                    newS.push({row:m.row,col:m.col})
                }
            }
        }else if(direction=="vertical"){
            const c = zero_based_idx;
            for(var i=0;i<this.rowCount;i++){
                const m = this.isMergedCell(i,c);
                if(!m){
                    newS.push({row:i,col:c})
                }else if(m.fromMerge.right==0){
                    newS.push({row:m.row,col:m.col})
                }
            }
        }
        return newS;
    }
    cellPosMapToShownPos(p:Position, recurSafety=0):Position{
        if(recurSafety>1){return{row:0,col:0};}
        const m = this.isMergedCell(p.row,p.col)?.fromMerge
        if(m && (m.row!=p.row || m.col!=p.col))
        {
            return this.cellPosMapToShownPos({row:m.row,col:m.col},recurSafety+1);
        }
        if(p.col==0){
            return {
                row:p.row,
                col:0
            }
        }
        const related:MergeInfo[] = [];
        for(var col=0;col<p.col;col++){
            const m = this.isMergedCell(p.row,col)?.fromMerge;
            if(m && !related.includes(m)){
                related.push(m);
            }
        }
        var shrinked = 0;
        related.forEach(m=>{
            shrinked+=m.right+1;
            if(p.row==m.row){
                shrinked-=1;
            }
        })
        return{
            row:p.row,
            col:p.col-shrinked
        }
    }
    shownPosMapToCellPos(p:Position):Position{
        const row = this.cells[p.row];
        const trueShownColMap:[number,number][] = [];
        const consideredMerge:MergeInfo[] = []
        let offset = 0;
        for(let col=0;col<row.length;col++){
            const m = this.isMergedCell(p.row, col)?.fromMerge;
            if(m && consideredMerge.indexOf(m)==-1){
                if(m.row == p.row){
                    trueShownColMap.push([col,col-offset]);
                    offset += m.right
                }
                else
                    offset += m.right + 1;
                consideredMerge.push(m);
            }
            if(!m){
                trueShownColMap.push([col,col-offset]);
            }
        }
        let trueCol = trueShownColMap.find(x=>x[1]==p.col)?.at(0)
        if(trueCol === undefined){
            trueCol = -1;
        }
        return{
            row:p.row,
            col:trueCol
        }
    }
    nextCellShown(p:Position){
        const first = this.cellsShown[0][0].position
        for(let r=0;r<this.cellsShown.length;r++){
            const row = this.cellsShown[r];
            for(let c=0;c<row.length;c++){
                const cell = row[c];
                if(p.row == cell.position.row && p.col == cell.position.col){
                    if(c!=row.length-1){
                        return row[c+1].position;
                    }else{
                        let tryR = r+1;
                        while(tryR < this.cellsShown.length){
                            const row = this.cellsShown[tryR];
                            if(row.length>0){
                                return row[0].position;
                            }
                            tryR++;
                        }
                        return first;
                    }
                }
            }
        }
    }

    //插入一行/一列
    insert(s:Selection|undefined,direction:SingleDirection):Selection{
        const gap = this.locateGap(s,direction);
        var newS = s||[];
        if(!gap){return s||[];}
        if(direction=="top"){
            this.cells.splice(gap.idx,0,this.newStringArr(this.colCount));
            //顶部靠着这个缝而且高度大于1的合并组会被往上拉高一格(位置不变，down+=1)
            //顶部高于这个缝而且底部低于这个缝的合并组会被往下拉高一格(位置不变，down+=1)
            //顶部低于(包括靠着且高度为1的)这个缝的合并组会被往下移动一格(row+=1，down不变)
            const extendMerges = this.merges.filter(m=>m.row==gap.idx && m.down>=1);
            const stretchMerges = this.merges.filter(m=>m.row<gap.idx && m.row+m.down>=gap.idx)
            const moveMerges = this.merges.filter(m=>m.row>gap.idx || (m.down==0 && m.row==gap.idx))
            extendMerges.forEach(m=>{
                this.cells[m.row][m.col] = this.cells[m.row+1][m.col];
                this.cells[m.row+1][m.col] = "";
                m.down+=1;
            })
            stretchMerges.forEach(m=>{m.down+=1})
            moveMerges.forEach(m=>{m.row+=1;})
            newS = this.selectSpecificIdx('horizontal',gap.idx)
        }
        else if(direction=="bottom"){
            if(gap.idx<this.rowCount){
                this.cells.splice(gap.idx,0,this.newStringArr(this.colCount));
            }else{
                this.cells.push(this.newStringArr(this.colCount));
            }
            //底部靠着这个缝而且高度大于1的合并组会被往下拉高一格(位置不变，down+=1)
            //顶部高于这个缝而且底部低于这个缝的合并组会被往下拉高一格(位置不变，down+=1)
            //顶部低于(包括靠着)这个缝的合并组会被往下移动一格(row+=1，down不变)
            const extendMerges = this.merges.filter(m=>{
                if(m.row+m.down+1==gap.idx && m.down>=1){return true;}
                if(m.row<gap.idx && m.row+m.down>=gap.idx){return true;}
                return false;
            })
            extendMerges.forEach(m=>{m.down+=1;})
            const moveMerges = this.merges.filter(m=>m.row>=gap.idx)
            moveMerges.forEach(m=>{m.row+=1;})
            newS = this.selectSpecificIdx('horizontal',gap.idx)
        }
        else if(direction=="left"){
            this.cells.forEach(r=>r.splice(gap.idx,0,""));
            //左侧靠着这个缝而且宽度大于1的合并组会被往左拉宽一格(位置不变，right+=1)
            //左部左于这个缝而且右侧右于这个缝的合并组会被往左拉宽一格(位置不变，right+=1)
            //左侧右于(包括靠着且宽度为1的)这个缝的合并组会被往右移动一格(col+=1，right不变)
            const extendMerges = this.merges.filter(m=>m.col==gap.idx && m.right>=1);
            const stretchMerges = this.merges.filter(m=>m.col<gap.idx && m.col+m.right>=gap.idx)
            const moveMerges = this.merges.filter(m=>m.col>gap.idx || (m.col==gap.idx && m.right==0))
            extendMerges.forEach(m=>{
                this.cells[m.row][m.col] = this.cells[m.row][m.col+1];
                this.cells[m.row][m.col+1] = "";
                m.right+=1;
            })
            stretchMerges.forEach(m=>{m.right+=1})
            moveMerges.forEach(m=>{m.col+=1;})
            newS = this.selectSpecificIdx('vertical',gap.idx)
        }
        else if(direction=="right"){
            if(gap.idx<this.colCount){
                this.cells.forEach(r=>r.splice(gap.idx,0,""));
            }else{
                this.cells.forEach(r=>r.push(""));
            }
            //右侧靠着这个缝而且宽度大于1的合并组会被往右拉宽一格(位置不变，right+=1)
            //左侧左于这个缝而且右侧右于这个缝的合并组会被往右拉宽一格(位置不变，right+=1)
            //左侧右于(包括靠着)这个缝的合并组会被往右移动一格(col+=1，right不变)
            const extendMerges = this.merges.filter(m=>{
                if(m.col+m.right+1==gap.idx && m.right>=1){return true;}
                if(m.col<gap.idx && m.col+m.right>=gap.idx){return true;}
                return false;
            })
            extendMerges.forEach(m=>{m.right+=1;})
            const moveMerges = this.merges.filter(m=>m.col>=gap.idx)
            moveMerges.forEach(m=>{m.col+=1;})
            newS = this.selectSpecificIdx('vertical',gap.idx)
        }

        if(direction=='top' || direction=='bottom'){
            const moveColors = this.colors.filter(cl=>cl.pos[0]>=gap.idx)
            moveColors.forEach(cl=>cl.pos[0]+=1);
        }
        else if(direction=='left' || direction=='right'){
            const moveColors = this.colors.filter(cl=>cl.pos[1]>=gap.idx)
            moveColors.forEach(cl=>cl.pos[1]+=1);
        }

        this.totalRefresh();
        return newS;
    }
    private locateGap(s:Selection|undefined,direction:SingleDirection):TableGap|undefined{
        if(!s){
            if(direction=="top"){
                return {idx:0, dir:"horizontal"}
            }else if(direction=="bottom"){
                const idx = this.rowCount;
                return {idx, dir:"horizontal"}
            }else if(direction=="left"){
                return {idx:0, dir:"vertical"}
            }else if(direction=="right"){
                const idx = this.colCount;
                return {idx, dir:"vertical"}
            }
            return undefined;
        }
        if(direction=="top"){
            const sRows = s.map(p=>p.row);
            const minRow = Math.min(...sRows);
            return {idx:minRow,dir:"horizontal"};
        }
        if(direction=="bottom"){
            const sRows = s.map(p=>{
                const m = this.isMergeInfoHead(p.row,p.col);
                if(m){
                    return m.row+m.down 
                }else{
                    return p.row
                }
            })
            const maxRow = Math.max(...sRows);
            return {idx:maxRow+1,dir:"horizontal"};
        }
        if(direction=="left"){
            const sCols = s.map(p=>p.col);
            const minCol = Math.min(...sCols);
            return {idx:minCol,dir:"vertical"};
        }
        if(direction=="right"){
            const sCols = s.map(p=>{
                const m = this.isMergeInfoHead(p.row,p.col);
                if(m){
                    return m.col+m.right
                }else{
                    return p.col
                }
            });
            const maxCol = Math.max(...sCols);
            return {idx:maxCol+1,dir:"vertical"};
        }
    }
    private newStringArr(size:number){
        return new Array(size).fill("");
    }

    exchange(gap:TableGap):boolean{
        if(gap.idx == 0){
            return false;
        }
        if(gap.dir == 'horizontal'){
            const rowTop = gap.idx-1;
            const rowBottom = gap.idx;
            if(rowBottom >= this.cells.length){
                return false;
            }
            //冲突检查
            let okay = true;
            this.enumerateRow(rowTop, (r, c)=>{
                const upperMerge = this.isMergedCell(r, c);
                const lowerMerge = this.isMergedCell(r+1, c);
                if(upperMerge && lowerMerge && upperMerge.fromMerge != lowerMerge.fromMerge){
                    if(upperMerge.fromMerge.down>0 || lowerMerge.fromMerge.down>0){
                        //冲突了
                        okay = false;
                        return false;
                    }
                }
                return true;
            })
            if(okay){
                this.enumerateRow(rowTop, (r, c)=>{
                    const upperMerge = this.isMergedCell(r, c);
                    const lowerMerge = this.isMergedCell(r+1, c);
                    if((!upperMerge || upperMerge.fromMerge.down==0) && (!lowerMerge || lowerMerge.fromMerge.down==0)){
                        const temp = this.cells[r][c];
                        this.cells[r][c] = this.cells[r+1][c];
                        this.cells[r+1][c] = temp;
                    }
                    return true;
                });
                this.merges.forEach(m=>{
                    if(m.down==0){
                        if(m.row == rowTop){
                            m.row = rowBottom;
                        }else if(m.row == rowBottom){
                            m.row = rowTop;
                        }
                    }
                })
                this.colors.forEach(cl=>{
                    if(cl.pos[0] == rowTop){
                        cl.pos[0] = rowBottom;
                    }else if(cl.pos[0] == rowBottom){
                        cl.pos[0] = rowTop;
                    }
                })
            }else{
                return false;
            }
        }
        else if(gap.dir == 'vertical'){
            const colLeft = gap.idx-1;
            const colRight = gap.idx;
            if(colRight >= this.colCount){
                return false;
            }
            //冲突检查
            let okay = true;
            this.enumerateCol(colLeft, (r, c)=>{
                const leftMerge = this.isMergedCell(r, c);
                const rightMerge = this.isMergedCell(r, c+1);
                if(leftMerge && rightMerge && leftMerge.fromMerge != rightMerge.fromMerge){
                    if(leftMerge.fromMerge.right>0 || rightMerge.fromMerge.right>0){
                        //冲突了
                        okay = false;
                        return false;
                    }
                }
                return true;
            })
            if(okay){
                this.enumerateCol(colLeft, (r,c)=>{
                    const leftMerge = this.isMergedCell(r, c);
                    const rightMerge = this.isMergedCell(r, c+1);
                    if((!leftMerge || leftMerge.fromMerge.right==0) && (!rightMerge || rightMerge.fromMerge.right==0)){
                        const temp = this.cells[r][c];
                        this.cells[r][c] = this.cells[r][c+1];
                        this.cells[r][c+1] = temp;
                    }
                    return true;
                })
                this.merges.forEach(m=>{
                    if(m.right==0){
                        if(m.col == colLeft){
                            m.col = colRight;
                        }else if(m.col == colRight){
                            m.col = colLeft;
                        }
                    }
                })
                this.colors.forEach(cl=>{
                    if(cl.pos[1] == colLeft){
                        cl.pos[1] = colRight;
                    }else if(cl.pos[1] == colRight){
                        cl.pos[1] = colLeft;
                    }
                })
            }
            else{
                return false;
            }
        }
        this.totalRefresh()
        return true;
    }

    //删除几行/几列
    delete(s:Selection,direction:Direction):Selection{
        if(direction=="horizontal"){
            const range = this.getSelectionRange(s,"vertical");
            const rangeLength = range.max - range.min + 1;
            //删除被完全吞没的合并组
            this.merges = this.merges.filter(m=>!this.rangeIncludeWholeMerge(range,m))
            this.colors = this.colors.filter(cl=>!this.rangeIncludeCell(range,cl.pos[0],cl.pos[1]))
            
            const moveMerges = this.merges.filter(m=>m.row>range.max);
            const shrinkUpwardMerges = this.merges.filter(m=>m.row<range.min && m.row+m.down>=range.min);
            const shrinkDownwardMerges = this.merges.filter(m=>m.row>=range.min && m.row<=range.max);
            moveMerges.forEach(m=>{m.row -= rangeLength})
            shrinkUpwardMerges.forEach(m=>{
                const amount = Math.min(rangeLength, m.row+m.down-range.min+1);
                m.down-=amount;
            })
            shrinkDownwardMerges.forEach(m=>{
                const shrinkAmount = range.max-m.row+1;
                m.down-=shrinkAmount;
                const moveAmount = m.row-range.min;
                this.cells[m.row+shrinkAmount][m.col]=this.cells[m.row][m.col];
                m.row-=moveAmount;
            })

            const moveColors = this.colors.filter(cl=>cl.pos[0]>range.max)
            moveColors.forEach(cl=>{cl.pos[0] -= rangeLength})

            this.cells.splice(range.min, rangeLength);
        }
        else if(direction=="vertical"){
            const range = this.getSelectionRange(s,"horizontal");
            const rangeLength = range.max - range.min + 1;
            //删除被完全吞没的合并组
            this.merges = this.merges.filter(m => !this.rangeIncludeWholeMerge(range, m))
            this.colors = this.colors.filter(cl=>!this.rangeIncludeCell(range,cl.pos[0],cl.pos[1]))

            const moveMerges = this.merges.filter(m => m.col > range.max);
            const shrinkLeftwardMerges = this.merges.filter(m => m.col < range.min && m.col + m.right >= range.min);
            const shrinkRightwardMerges = this.merges.filter(m => m.col >= range.min && m.col <= range.max);
            moveMerges.forEach(m => { m.col -= rangeLength; })
            shrinkLeftwardMerges.forEach(m => {
                const amount = Math.min(rangeLength, m.col + m.right - range.min + 1);
                m.right -= amount;
            })
            shrinkRightwardMerges.forEach(m => {
                const shrinkAmount = range.max - m.col + 1;
                m.right -= shrinkAmount;
                const moveAmount = m.col-range.min;
                this.cells[m.row][m.col+shrinkAmount] = this.cells[m.row][m.col];
                m.col-=moveAmount;
            })

            const moveColors = this.colors.filter(cl=>cl.pos[1]>range.max)
            moveColors.forEach(cl=>{cl.pos[1] -= rangeLength})

            this.cells.forEach(r=>{
                r.splice(range.min,rangeLength);
            })
        }
        this.totalRefresh();
        return [];
    }

    //仅用于辅助测试，暂无实际功能
    rotateTableClockwise(times:0|1|2|3 = 1, s?:Selection|undefined):Selection{
        if(times==0){return s||[];}
        for(var t=1;t<=times;t++){
            const newCells:string[][] = [];
            for(var r=0;r<this.colCount;r++){
                newCells.push([]);
            }
            this.cells.forEach((r,rIdx)=>{
                r.forEach((c,cIdx)=>{
                    newCells[cIdx][this.rowCount-1-rIdx] = c; 
                })
            })
            this.cells = newCells;

            const newMerges:MergeInfo[] = [];
            this.merges.forEach(m=>{
                const leftBottom:Position = {
                    row:m.row+m.down,
                    col:m.col
                }
                newMerges.push({
                    row:leftBottom.col,
                    col:this.rowCount-1-leftBottom.row,
                    down:m.right,
                    right:m.down
                })
            })
            this.merges = newMerges;

            if(s){
                var newS:Selection = [];
                s.forEach(p=>{
                    newS.push({
                        row:p.col,
                        col:this.rowCount-1-p.row
                    }) 
                })
                s = newS;
            }
            this.refreshSize();
        }
        this.refreshMergedPositions();
        this.refreshCellShown();
        return s||[];
    }
}

export const tableDefaultValue = {
    id:0,name:"testTable",
    cells:
    [
        ["数据加载异常，请检查网络连接或联系管理员","","","",""],
        ["1A","1B","1C","1D","1E"],["2A","2B","2C","2D","2E"],["3A","3B","3C","3D","3E"]
    ],
    merges:[{row:0,col:0,right:4,down:0}]
}